/*
 * marker.h
 *
 *  Created on: Jul 28, 2010
 *      Author: Pierre Lindenbaum PhD
 *              plindenbaum@yahoo.fr
 *              http://plindenbaum.blogspot.com
 *              
 */
#ifndef TRANS_PEDIGREE_H
#define TRANS_PEDIGREE_H
#include <iostream>
#include <string>
#include <cassert>
#include <sstream>
#include <cstdio>
#include <set>
#include "io/gzstreambuf.h"
#include "util/tokenizer.h"

class Family;
class Population;

enum Sex
    {
    unknown=0,
    male=1,
    female=2
    };

/**
 * Individual
 */
class Individual
    {
    private:
	    /** family for this individual */
	    Family* _family;
	    /** individual's name */
	    std::string _id;
	    /** link to father or NULL */
	    Individual* _father;
	    /** link to mother or NULL */
	    Individual* _mother;
	    /** sex is good */
	    Sex _sex;
	    /** children */
	    std::set<const Individual*> children;
	    /** index in pedigree file */
	    int _index;
	    Individual():_family(NULL),_father(NULL),_mother(NULL),_sex(unknown),_index(-1)
		{

		}
    public:
	    virtual ~Individual()
		{
		}

	    int index() const
		{
		return _index;
		}

	   const char* name() const
	       {
	       return _id.c_str();
	       }

	   const Family* family() const
		{
		return _family;
		}

	   const Population* population() const
		{
		return family()->population();
		}

	   const Individual* parent(int index) const
		{
	        assert(index==0 || index==1);
	        return (index==0?father():mother());
		}



	   const Individual* father() const
		{
		return _father;
		}

	   const Individual* mother() const
		{
		return _mother;
		}

	   bool hasFather() const
	       {
	       return father()!=NULL;
	       }

	   bool hasMother() const
	       {
	       return mother()!=NULL;
	       }

	   bool hasParent() const
	       {
	       return hasFather() || hasMother();
	       }

	   bool hasParent(const Individual* parent) const
	       {
	       return parent!=NULL && (father()==parent || mother()==parent);
	       }

	   bool isFatherOf(const Individual* child) const
	       {
	       return child!=NULL && child->father()==this;
	       }

	   bool isMotherOf(const Individual* child) const
	       {
	       return child!=NULL && child->mother()==this;
	       }

	   bool isParentOf(const Individual* child) const
	       {
	       return isFatherOf(child) || isMotherOf(child);
	       }

	   Sex sex() const
	       {
	       return _sex;
	       }

    friend class Family;
    friend  std::ostream& operator << (std::ostream& out,const Individual& o);
    };

typedef Individual* IndividualPtr;

std::ostream& operator << (std::ostream& out,const Individual& o)
    {
    out << o.family()->name() << "\t" << o.name() << "\t";
    out << (o.father()==NULL?"0":o.father()->name());
    out << "\t";
    out << (o.mother()==NULL?"0":o.mother()->name());
    out << "\t" << o.sex()<< "\t";
    out << o.population()->name();
    return out;
    }

/**
 *
 * Family
 *
 */
class Family
    {
    protected:
	    /** population for this family */
	    Population* _population;
	    /** all individuals for this family */
	    std::vector<IndividualPtr> individuals;
	    /** family name */
	    std::string _id;

	    Family()
		{
		}
    public:
	   typedef int size_type;

	   virtual ~Family()
		{
		while(!individuals.empty())
		    {
		    delete individuals.back();
		    individuals.pop_back();
		    }
		}


	   const char* name() const
	       {
	       return _id.c_str();
	       }

	   const Population* population() const
		{
		return _population;
		}

	   size_type size() const
		{
		return (size_type)individuals.size();
		}

	    const IndividualPtr at(size_type i) const
		{
		return individuals.at((std::vector<IndividualPtr>::size_type)i);
		}

	    const IndividualPtr operator[](size_type i) const
		{
		return at(i);
		}

	    const IndividualPtr findByName(const char* s) const
	       {
		for(Family::size_type i=0;i< o.size();++i)
		    {
		    const IndividualPtr indi=at(i);
		    if(std::strcmp(indi->name(),s)==0) return indi;
		    }
	       return NULL;
	       }

    friend class Population;
    friend  std::ostream& operator << (std::ostream& out,const Family& o);
    };

typedef Family* FamilyPtr;

std::ostream& operator << (std::ostream& out,const Family& o)
    {
    for(Family::size_type i=0;i< o.size();++i)
    	{
    	out << *(o.at(i)) << std::endl;
    	}
    return out;
    }

/**
 *
 * Population
 *
 */
class Population
    {
    protected:
	    std::vector<FamilyPtr> families;
	    std::string _id;

	    Population()
		{
		}
    public:
	    typedef int size_type;

	    virtual ~Population()
		{
		while(!families.empty())
		    {
		    delete families.back();
		    families.pop_back();
		    }
		}

	    const char* name() const
	       {
	       return _id.c_str();
	       }

	    size_type size() const
		{
		return (size_type)families.size();
		}

	    const FamilyPtr at(size_type i) const
		{
		return families.at((std::vector<FamilyPtr>::size_type)i);
		}

	    const FamilyPtr operator[](size_type i) const
		{
		return at(i);
		}

	    const FamilyPtr findByName(const char* s) const
	       {
		for(Family::size_type i=0;i< o.size();++i)
		    {
		    const FamilyPtr f=at(i);
		    if(std::strcmp(f->name(),s)==0) return f;
		    }
	       return NULL;
	       }
    friend  std::ostream& operator << (std::ostream& out,const Population& o);
    };

typedef Population* PopulationPtr;

std::ostream& operator << (std::ostream& out,const Population& o)
    {
    for(Population::size_type i=0;i< o.size();++i)
	{
	out << *(o.at(i));
	}
    return out;
    }

/**
 *
 * PopulationList
 *
 */
class PopulationList
    {
    protected:
	    std::vector<PopulationPtr> populations;
	    PopulationList()
		{
		}
    public:
	    typedef int size_type;


	    virtual ~PopulationList()
		{
		while(!populations.empty())
		    {
		    delete populations.back();
		    populations.pop_back();
		    }
		}

	    size_type size() const
	    	    {
	    	    return (size_type)PopulationPtr.size();
	    	    }

	    const PopulationPtr at(size_type i) const
		{
		return populations.at((std::vector<PopulationPtr>::size_type)i);
		}

	    const PopulationPtr operator[](size_type i) const
		{
		return at(i);
		}

	    const PopulationPtr findByName(const char* s) const
	       {
		for(Family::size_type i=0;i< o.size();++i)
		    {
		    const PopulationPtr p=at(i);
		    if(std::strcmp(p->name(),s)==0) return f;
		    }
	       return NULL;
	       }
    friend  std::ostream& operator << (std::ostream& out,const PopulationList& o);
    };

typedef PopulationList* PopulationListPtr;

std::ostream& operator << (std::ostream& out,const PopulationList& o)
    {
    for(PopulationList::size_type i=0;i< o.size();++i)
	{
	out << *(o.at(i));
	}
    return out;
    }

/**
 *
 * PopulationListFactory
 *
 */
class PopulationListFactory
    {
    private:
	    void crlf(std::string& line)
		{
		if(!line.empty() && line.at(line.size()-1)=='\r')
			{
			line.erase(line.size()-1,1);
			}
		}
    protected:
		virtual int populationColumn() const
		    {
		    return 6;
		    }

		virtual int familyColumn() const
		    {
		    return 0;
		    }

		virtual int nameColumn() const
		    {
		    return 1;
		    }

		virtual int fatherColumn() const
		    {
		    return 2;
		    }

		virtual int motherColumn() const
		    {
		    return 3;
		    }

		virtual int sexColumn() const
		    {
		    return 4;
		    }
    public:
	PopulationListFactory()
	    {
	    }

	virtual ~PopulationListFactory()
	    {
	    }



	PopulationListPtr parse(const char* filename)
	    {
	    std::vector<std::pair<IndividualPtr,std::string> > fathers;
	    std::vector<std::pair<IndividualPtr,std::string> > mothers;
	    PopulationListPtr list=new PopulationList();
	    u915::io::gzinbuf buff(filename,100000);
	    std::iostream in(&buff);
	    std::string line;
	    int nLine=-1;
	    PopulationPtr defaultPopulation=NULL;
	    if(populationColumn()<0)
		{
		defaultPopulation=new Population();
		defaultPopulation->assign("DEFAULT.POPULATION");
	    	list->populations.push_back(defaultPopulation);
		}


	    while(std::getline(in,line))
		{
		++nLine;
		crlf(line);
		u915::util::Tokenizer tokens(line,'\t');

		PopulationPtr population=NULL;
		if(populationColumn()>=0)
		    {
		    population=(PopulationPtr)(list->findByName(tokens[populationColumn()].c_str()));
		    if(population==NULL)
			{
			population=new Population();
			population->_id=tokens[populationColumn()];
			list->populations.push_back(population);
			}
		    }
		else
		    {
		    population = defaultPopulation;
		    }

		FamilyPtr family=(FamilyPtr)(list->findByName(tokens[familyColumn()].c_str()));
		if(family==NULL)
		    {
		    family=new Family();
		    family->_population=population;
		    family->_id=tokens[familyColumn()];
		    population->families.push_back(family);
		    }
		IndividualPtr indi= new Individual();
		indi->_index=nLine;
		indi->_id=tokens[nameColumn()];
		indi->_family=family;
		family->individuals.push_back(indi);
		if(tokens[fatherColumn()].compare("0")!=0)
		    {
		    fathers.push_back(std::make_pair<IndividualPtr,std::string>(indi,tokens[fatherColumn()]));
		    }
		if(tokens[motherColumn()].compare("0")!=0)
		    {
		    mothers.push_back(std::make_pair<IndividualPtr,std::string>(indi,tokens[motherColumn()]));
		    }
		if(tokens[sexColumn()].compare("1")==0)
		    {
		    indi->_sex=male;
		    }
		else if(tokens[sexColumn()].compare("2")==0)
		    {
		    indi->_sex=female;
		    }
		else if(tokens[sexColumn()].compare("0")==0)
		    {
		    indi->_sex=unknown;
		    }
		else
		    {
		    std::ostringstream os;
		    os << "bad sex " << tokens[sexColumn()];
		    throw std::runtime_error(os.str());
		    }
		}

	    for(int side=0;side< 2;++side)
		{
		std::vector<std::pair<IndividualPtr,std::string> > & parents=(side==0?fathers:mothers);
		for(std::size_t i=0;i< parents.size();++i)
		    {
		    IndividualPtr p=(IndividualPtr)(parents.at(i).first->family()->findByName(parents.at(i).second.c_str()));
		    if(p==NULL)
			{
			std::ostringstream os;
			os << "Cannot find parent "<< parents.at(i).second << " for "<< parents.at(i).first->name();
			throw std::runtime_error(os.str());
			}
		    if(parents.at(i).first==p)
			{
			std::ostringstream os;
		    	os << "parent of itself ? "<< parents.at(i).second;
		    	throw std::runtime_error(os.str());
			}
		    p->children.add(parents.at(i).first);

		    if(side==0)
			{
			if(p->sex()==female)
			    {
			    std::ostringstream os;
			    os << "expected "<< p->name << " to be a MALE father of "
			       << parents.at(i).first->name();
			    throw std::runtime_error(os.str());
			    }
			parents.at(i).first->_father=p;
			}
		    else
			{
			if(p->sex()==male)
			    {
			    std::ostringstream os;
			    os << "expected "<< p->name << " to be a FEMALE mother of "
			       << parents.at(i).first->name();
			    throw std::runtime_error(os.str());
			    }
			parents.at(i).first->_mother=p;
			}
		    }
		}

	    return list;
	    }
    };



#endif
